package com.g2room.java8;

import com.g2room.model.Admin;
import com.g2room.model.User;

import java.io.File;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Lambda示例，有以下几类：<br>
 * 1. 不需要参数,返回值为 5 <br>
 * () -> 5 <p/>
 * 2. 接收一个参数(数字类型),返回其2倍的值 <br>
 * x -> 2 * x <p/>
 * 3. 接受2个参数(数字),并返回他们的差值 <br>
 * (x, y) -> x – y <p/>
 * 4. 接收2个int型整数,返回他们的和 <br>
 * (int x, int y) -> x + y <p/>
 * 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) <br>
 * (String s) -> System.out.print(s) <p/>
 * 注意：lambda 表达式只能引用标记了 final 的外层局部变量，这就是说不能在 lambda 内部修改定义在域外的局部变量，否则会编译错误。
 */
public class LambdaSample {

    /**
     * 集合接口有两个方法来生成流：
     * stream() − 为集合创建串行流。
     * parallelStream() − 为集合创建并行流。
     * Stream有以下方法：
     * forEach - 迭代流中的每个数据
     * map - 映射每个元素到对应的结果。即转换为另外一个对象。
     * flatmap - 将一个2维的集合映射成一个一维。
     * filter - 通过设置的条件过滤出元素。
     * limit - 用于获取指定数量的流。
     * distinct - 排重。
     * sorted - 对流进行排序。sorted只是创建一个流对象排序的视图，而不会改变原来集合中元素的顺序。
     * parallelStream - 流并行处理程序的代替方法。
     * collect - 规约操作，Collectors。
     * mapToInt、mapToDouble、mapToLong - 转换为数字流，方便进行总计、平均、最大、最小。
     * match（noneMatch、allMatch、anyMatch） - 匹配操作有多种不同的类型，都是用来判断某一种规则是否与流对象相互吻合的。所有的匹配操作都是终结操作，只返回一个boolean类型的结果。
     * count - Count是一个终结操作，它的作用是返回一个数值，用来标识当前流对象中包含的元素数量。
     * reduce - 该操作是一个终结操作，它能够通过某一个方法，对元素进行削减操作。该操作的结果会放在一个Optional变量里返回。
     */
    public void streamSample() {
        System.out.println("********************* Stream *********************");
        List<User> users = User.generateUsers(3);
        // filter过滤使用
        List<User> users1 = users.stream().filter(u -> u.getAge() > 35 && u.getSex() == "男").collect(Collectors.toList());
        // 上面等同于下面的语句，即使用两个filter
        List<User> users2 = users.stream().filter(u -> u.getAge() > 35).filter(u -> u.getSex() == "男").collect(Collectors.toList());
        System.out.println("users2.equals(users1) = " + users2.equals(users1)); // true
        // 上述可以用下面的方法类实现（策略模式的思路）。这样可以实现方法的封装，把变化的部分代码抽象出来。
        // 这种方式就能很好的把之前，大部分代码相同，但是有某一行或几个条件不一样的判断的地方而导致需要写两个或多个方法
        Predicate<User> ageFilter = u -> u.getAge() > 2;
        Predicate<User> sexFilter = u -> u.getSex() == "男"; // 这里改变了下条件，使得两边不相等
        List<User> users3 = filterUser(filterUser(users, ageFilter), sexFilter);

        // sort排序，下面两种写法一样的效果
        users3.sort((u1, u2) -> u1.getAge().compareTo(u2.getAge()));
        users3.sort(Comparator.comparingInt(User::getAge));
        // 以下两种写法一样
        users3.stream().sorted((u1, u2) -> u1.getAge() - u2.getAge()).collect(Collectors.toList());
        users3.stream().sorted(Comparator.comparingInt(User::getAge)).collect(Collectors.toList());
        System.out.println("sorted = " + users3);

        // groupingBy结果分组，按性别
        Map<String, List<User>> userMap = users.stream().collect(Collectors.groupingBy(User::getSex));
        System.out.println("groupingBy = " + userMap);

        // mapToInt使用。计算平均年龄、年龄之和，还有max和min一样的使用
        OptionalDouble avgAge = users.stream().mapToInt(User::getAge).average();
        int sumAge = users.stream().mapToInt(User::getAge).sum();
        // 也可以这样使用
        IntSummaryStatistics intSummaryStatistics = users.stream().mapToInt(User::getAge).summaryStatistics();
        System.out.println("avgAge = " + avgAge.getAsDouble() + "，sumAge = " + sumAge
                + "\navgAge = " + intSummaryStatistics.getAverage() + "，sumAge = " + intSummaryStatistics.getSum());

        // map转换为另外一个对象Admin，也可以是Integer等其他对象
        List<Admin> admins = users.stream().filter(u -> null != u)
                .map(u -> Admin.builder().age(u.getAge()).name(u.getName()).userName("un" + u.getId()).sex(u.getSex()).build())
                .collect(Collectors.toList());
        System.out.println(admins);

        // flatMap二维数组遍历转变为一维
        List<Integer> int1 = IntStream.range(0, 4).boxed().distinct().collect(Collectors.toList());
        List<Integer> int2 = IntStream.range(10, 14).boxed().distinct().collect(Collectors.toList());
        List<List<Integer>> ints = new ArrayList<List<Integer>>();
        ints.add(int1);
        ints.add(int2);
        List<Integer> int9 = ints.stream().flatMap(i -> i.stream()).collect(Collectors.toList());
        System.out.println("flatMap==" + ints + " --> " + int9);

        // match示例
        boolean agegt10 = users.stream().allMatch(u -> u.getAge() > 10);
        boolean ageOr10 = users.stream().anyMatch(u -> u.getAge() > 10);
        boolean agenone10 = users.stream().noneMatch(u -> u.getAge() > 10);
        System.out.println("agegt10 = " + agegt10 + "= ageOr10 =" + ageOr10 + "= agenone10 =" + agenone10);

        List<String> strList = getList(1000);
        // Reduce示例
        Optional<String> stringOptional = strList.stream().limit(10).reduce((s1, s2) -> s1 + "@" + s2);
        System.out.println("reduce = " + stringOptional.get());

        // 测试并行和串行。注意：不是所有场景下并行都比串行的效率高。比如使用自带的sorted，并行反而慢。
        // 详见：https://blog.csdn.net/sinat_31465609/article/details/93498840
        long t0 = System.nanoTime();
        // 串行
//        strList.stream().sorted().collect(Collectors.toList());
        strList.stream().forEach(s -> {
            String ss = s + "1";
        });
        long t1 = System.nanoTime();
        long t00 = System.nanoTime();
        // 并行
//        strList.parallelStream().sorted().collect(Collectors.toList());
        strList.parallelStream().forEach(s -> {
            String ss = s + "1";
        });
        long t2 = System.nanoTime();
        System.out.println("串行耗时：" + (t1 - t0) + "，并行耗时：" + (t2 - t00));
    }

    public List<User> filterUser(List<User> user, Predicate<User> predicate) {
        return user.stream().filter(predicate).collect(Collectors.toList());
    }

    /**
     * Optional不是一个函数式接口，而是一个精巧的工具接口，用来防止NullPointerException产生。
     * ofNullable和of方法针对的是是否允许null值
     * 使用注意：Optional 不是 Serializable。因此，它不应该用作类的字段。
     * Optional 主要用作返回类型。在获取到这个类型的实例后，如果它有值，你可以取得这个值，否则可以进行一些替代行为。
     * Optional 类有一个非常有用的用例，就是将其与流或其它返回 Optional 的方法结合，以构建流畅的API。
     * Java 9 为 Optional 类添加了三个方法：or()、ifPresentOrElse() 和 stream()：
     * or() 方法与 orElse() 和 orElseGet() 类似，它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。
     * ifPresentOrElse() 方法需要两个参数：一个 Consumer 和一个 Runnable。如果对象包含值，会执行 Consumer 的动作，否则运行 Runnable。
     * stream()通过把实例转换为 Stream 对象
     */
    public void optionalsSample() {
        System.out.println("********************* Optional *********************");
        // 创建一个空值
        Optional<User> emptyOpt = Optional.empty();
        System.out.println("emptyOpt.isPresent = " + emptyOpt.isPresent()); // false
        User user = null;
        // 此处演示了Supplier用法。所以有点复杂
        Supplier<User> userSupplier = () -> User.builder().id(1).build();
        Optional<User> optionalUser = Optional.of(userSupplier.get());
        Optional<User> optional1 = Optional.ofNullable(user);
        System.out.println("emptyOpt.isPresent = " + optional1.isPresent() + " : " + optionalUser.isPresent()); // 判断是否为null
        // 取出值使用get方法，判断不为空isPresent或ifPresent。正常都会在不为空时做一些操作，如下
        User user1 = optionalUser.get();
        optionalUser.ifPresent(u -> {
            // 此处写业务逻辑代码
            System.out.println("ifPresent = " + u);
        });
        // orElse()，作为备胎，如果当前Optional有值则返回该值，否则返回orElse里面的值。
        User user2 = optional1.orElse(user1);
        System.out.println("orElse = " + user2);
        // orElseGet()，作用同orElse()，不同之处在于Optional对象含非空值时，它不会调用内部方法；但是orElse会。
        User user3 = optionalUser.orElse(getUser(3));
        User user4 = optionalUser.orElseGet(() -> getUser(4));
        System.out.println("orElseGet And orElse = " + user3 + " == " + user4);
        // Optional也支持filter() 和map()方法转换。以下为链式调用
        int id = optional1.map(User::getId).orElse(0);
        System.out.println("id = " + id);
    }

    public void lambdaSample() {
        System.out.println("********************* Lambda *********************");
        /**
         内置了四大函数式接口（对应的还有基本类型的，如IntConsumer、IntFunction、DoublePredicate等）：
         1. 消费型接口： Consumer< T> void accept(T t)有参数，无返回值的抽象方法；
         2. 供给型接口： Supplier < T> T get() 无参有返回值的抽象方法；
         3. 断定型接口： Predicate< T> boolean test(T t):有参，但是返回值类型是固定的boolean
         4. 函数型接口： Function< T，R> R apply(T t)有参有返回值的抽象方法。BiFunction是可以传两个参数
         */
        int i = 1000;
        Integer ii = 10000;
        Consumer<String> consumer = a -> {
            System.out.println(a);
        };

        consumer.accept("Consumer println");
        Supplier<String> supplier = () -> "Supplier = ";
        System.out.println(supplier.get());
        Predicate<Integer> predicate = a -> a > 10;
        System.out.println("Predicate = " + predicate.test(5));

        // 定义函数式接口，可以使用外部的参数
        Function<Integer, Integer> add1 = (a) -> a + 100 + i + ii;
        System.out.println("Function=" + add1.apply(2));
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b + i + ii;
        // 作为参数传入方法内调用，便于封装不变化部分
        System.out.println(mathOperation(1, 2, add, add1));

        List<String> strings = getList(3);
        System.out.print("移除前=");
        strings.stream().forEach(System.out::print);
        strings.removeIf(s -> s.equals("a2"));
        strings.removeIf(s -> s.equals("a55"));
        System.out.print("，removeIf后=");
        strings.stream().forEach(System.out::print);
        System.out.println();
    }

    /**
     * 传入lambda表达式来计算。
     *
     * @param a
     * @param b
     * @param biFunction
     * @param function
     * @return
     */
    public static int mathOperation(final int a, final int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(a, b);
    }

    /**
     * putIfAbsent - 避免我们将null值写入
     */
    public void mapSample() {
        Map<Integer, User> userMap = getUserMap(4);
        userMap.compute(0, (k, v) -> {
            v.setAge(v.getAge() + 10000000);
            return v;
        });

    }

    private static User getUser(int id) {
        System.out.println("create user, id is " + id);
        return User.builder().id(id).build();
    }

    public static Map<Integer, User> getUserMap(int i) {
        Map<Integer, User> userMap = new HashMap<>();
        IntStream.range(0, i).forEach(n -> {
            // putIfAbsent避免我们将null写入
            userMap.putIfAbsent(n, User.generateUser(n));
        });
        return userMap;
    }

    public static List<String> getList(int n) {
        List<String> lists = new ArrayList<>();
        IntStream.range(0, n).forEach(i -> lists.add("a" + i));
        return lists;
    }

}
